package com.russell.mail.store;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.mail.Address;
import javax.mail.FetchProfile;
import javax.mail.Flags.Flag;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.UIDFolder;
import javax.mail.internet.InternetAddress;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sun.mail.imap.IMAPFolder;
import com.sun.mail.pop3.POP3Folder;

public class MailStoreEngine {

	private Folder folder;

	private String host;

	protected final Logger logger = LoggerFactory.getLogger(getClass());

	private String password;

	private int port;

	private String protocol;

	private Session session;

	private boolean sslEnabled;

	private Store store;

	private String username;

	/**
	 * Sets properties and attempts to connect to the POP store. <a
	 * href="mailto:fvinluan@gmail.com">Francis Vinluan</a>
	 * 
	 * @return void
	 * @throws Exception
	 */
	protected void activateOptions() throws Exception {
		if (logger.isDebugEnabled()) {
			logger.debug("Activating Options");
		}

		if (session == null) {
			logger.debug("Opening session using props");
			Properties props = new Properties();
			session = Session.getDefaultInstance(props, null);
		}

		if (StringUtils.isNotEmpty(protocol) && StringUtils.isNotEmpty(host)
				&& StringUtils.isNotEmpty(username)
				&& StringUtils.isNotEmpty(password)) {

			store = session.getStore(protocol);
			if (port != 0) {
				store.connect(host, port, username, password);
			} else {
				store.connect(host, username, password);
			}

		} else {
			Properties prop = session.getProperties();
			store = session.getStore();

			if (StringUtils.isEmpty(store.getURLName().getHost())
					&& StringUtils.isEmpty(store.getURLName().getUsername())
					&& StringUtils.isEmpty(store.getURLName().getPassword())) {

				String storeHost = prop.getProperty("mail.host");
				String storeUserName = prop.getProperty("mail.user");
				String storePassword = prop.getProperty("mail.password");
				store.connect(storeHost, storeUserName, storePassword);
			} else {
				store.connect();
			}
		}

	}

	public void closeFolder() {
		try {
			folder.close(true);
		} catch (MessagingException e) {
			logger.warn("Closing folder fails", e);
		}
	}

	// /**
	// * Activates all SSL settings. <a
	// * href="mailto:fvinluan@gmail.com">Francis Vinluan</a>
	// *
	// * @return void
	// * @throws MessagingException
	// */
	// private void activateTrustAllHost() throws Exception {
	// if (logger.isDebugEnabled()) {
	// logger.debug("Activating SSL");
	// }
	//
	// Properties prop = session.getProperties();
	//
	// MailSSLSocketFactory sf = new MailSSLSocketFactory();
	// sf.setTrustAllHosts(true);
	// prop.put("mail.pop3.ssl.enable", "true");
	// prop.put("mail.pop3.ssl.socketFactory", sf);
	//
	// String host = prop.getProperty("mail.pop3.host");
	// String user = prop.getProperty("mail.user");
	// String pass = prop.getProperty("mail.password");
	//
	// URLName url = new URLName("pop3", host, 995, "", user, pass);
	//
	// session = Session.getInstance(prop, null);
	// store = new POP3SSLStore(session, url);
	// store.connect();
	//
	// }

	public void connect() throws Exception {
		if (logger.isDebugEnabled()) {
			logger.debug("Connecting to " + host);
		}
		activateOptions();
	}

	public void deleteMessage(MailStoreMessage message)
			throws MessagingException {
		if (logger.isDebugEnabled()) {
			logger.debug("Delete Message " + message.getId());
		}
		FetchProfile fp = new FetchProfile();
		fp.add(UIDFolder.FetchProfileItem.UID);
		folder.fetch(folder.getMessages(), fp);

		try {
			Message m = folder.getMessage(message.getMessageNumber());
			m.setFlag(Flag.DELETED, true);
		} catch (IndexOutOfBoundsException iex) {
			if (logger.isInfoEnabled()) {
				logger.info("Message number out of range");
			}
		}

	}

	public void disconnect() throws Exception {
		if (logger.isDebugEnabled()) {
			logger.debug("Disconnecting from Mail Store");
		}
		if (folder != null && folder.isOpen()) {
			closeFolder();
		}

		if (store != null && store.isConnected()) {
			store.close();
		}
	}

	public void downloadAttachments(int messageNo, String destination)
			throws IOException, MessagingException {
		if (logger.isDebugEnabled()) {
			logger.debug("Downloading Attachements of " + messageNo + " to "
					+ destination);
		}
		Part part = folder.getMessage(messageNo);
		File dstPath = new File(destination);

		if (!dstPath.exists()) {
			if (!dstPath.mkdirs()) {
				throw new IOException("Create Directory Failed");
			}
		}

		Object content = part.getContent();
		if (content instanceof Multipart) {
			for (int i = 0; i < ((Multipart) content).getCount(); i++) {
				part = ((Multipart) content).getBodyPart(i);

				if (part.getFileName() != null) {

					InputStream in = part.getInputStream();
					OutputStream out = null;
					try {
						out = new FileOutputStream(new File(dstPath,
								part.getFileName()));
						byte[] buf = new byte[1024];

						int len;
						while ((len = in.read(buf)) > 0) {
							out.write(buf, 0, len);
						}

						in.close();
						out.flush();
					} finally {
						if (null != out) {
							out.close();
						}
					}

				}
			}
		}

	}

	/**
	 * Return the list of attachments of the message.
	 * 
	 * 
	 * @param part
	 * @return List
	 * @throws IOException
	 * @throws MessagingException
	 */
	private List<String> getAttachments(Part part) throws MessagingException,
			IOException {
		if (logger.isDebugEnabled()) {
			logger.debug("Retrieving Attachments");
		}
		List<String> attachments = new ArrayList<String>();

		Object content = part.getContent();
		if (content instanceof Multipart) {
			for (int i = 0; i < ((Multipart) content).getCount(); i++) {
				part = ((Multipart) content).getBodyPart(i);

				if (part.getFileName() != null) {
					if (logger.isDebugEnabled()) {
						logger.debug("Retrieved " + part.getFileName());
					}
					attachments.add(part.getFileName());
				}
			}
		}
		return attachments;
	}

	public MailStoreMessage getMessage(int messageNo)
			throws MessagingException, IOException {
		MailStoreMessage message = new MailStoreMessage();
		message.setMessageNumber(messageNo);

		Message m = null;

		try {
			FetchProfile fp = new FetchProfile();
			fp.add(UIDFolder.FetchProfileItem.UID);
			folder.fetch(folder.getMessages(), fp);

			m = folder.getMessage(messageNo);

			if (folder instanceof com.sun.mail.pop3.POP3Folder) {
				message.setId(((POP3Folder) folder).getUID(m));
				if (logger.isDebugEnabled()) {
					logger.debug("Retrieved POP3 ID " + message.getId());
				}
			} else if (folder instanceof com.sun.mail.imap.IMAPFolder) {

				String uid = String.valueOf(((IMAPFolder) folder).getUID(m));

				message.setId(uid);
				if (logger.isDebugEnabled()) {
					logger.debug("Retrieved IMAP ID " + message.getId());
				}
			}

			// SUBJECT
			message.setSubject(m.getSubject());

			// DATE SENT
			message.setDateSent(m.getSentDate());

			// DATE RECEIVED
			message.setDateReceived(m.getReceivedDate());

			// SENDERS
			String sender = getSender(m);
			message.setFrom(sender);

			// RECIPIENTS
			String recipients = getRecipients(m);
			message.setTo(recipients);

			// BODY
			String text = getText(m);
			message.setText(text);

			// ATTACHMENTS
			message.setAttachments(getAttachments(m));
			if (logger.isDebugEnabled()) {
				logger.debug("Retrieved " + message.toString());
			}

		} catch (IndexOutOfBoundsException iex) {
			logger.warn("Message number out of range");
		}
		return message;
	}

	public int getMessageCount() throws Exception {
		return folder.getMessageCount();
	}

	public List<MailStoreMessage> getMessages() throws MessagingException,
			IOException {
		if (logger.isDebugEnabled()) {
			logger.debug("Getting Messages");
		}
		List<MailStoreMessage> messages = new ArrayList<MailStoreMessage>();

		for (int i = 0; i < folder.getMessageCount(); i++) {
			if (logger.isDebugEnabled()) {
				logger.debug("Retrieving message " + i);
			}
			MailStoreMessage message = getMessage(i + 1);
			messages.add(message);
		}

		return messages;
	}

	public int getNewMessageCount() throws Exception {
		return folder.getNewMessageCount();
	}

	/**
	 * Return the recipients of the message.
	 * 
	 * @param message
	 * @return String
	 * @throws MessagingException
	 */
	private String getRecipients(Message message) throws MessagingException {
		Address[] addresses = message.getRecipients(Message.RecipientType.TO);
		StringBuffer recipients = new StringBuffer();
		if (addresses != null) {
			for (Address address : addresses) {
				if (address instanceof InternetAddress) {
					if (StringUtils.isEmpty(recipients.toString())) {
						String addStr = ((InternetAddress) address)
								.getAddress();
						recipients.append(StringUtils.trimToNull(addStr));
					} else {
						String addStr = ((InternetAddress) address)
								.getAddress();
						recipients.append(", ");
						recipients.append(StringUtils.trimToNull(addStr));
					}
				} else {
					if (StringUtils.isEmpty(recipients.toString())) {
						String addStr = address.toString();
						recipients.append(StringUtils.trimToNull(addStr));
					} else {
						String addStr = address.toString();
						recipients.append(", ");
						recipients.append(StringUtils.trimToNull(addStr));
					}
				}
			}

		}
		return recipients.toString();

	}

	/**
	 * Return the senders of the message.
	 * 
	 * @param message
	 * @return String
	 * @throws MessagingException
	 */
	private String getSender(Message message) throws MessagingException {
		Address[] addresses = message.getFrom();
		StringBuffer senders = new StringBuffer();
		if (addresses != null) {
			for (Address address : addresses) {
				if (address instanceof InternetAddress) {
					if (StringUtils.isEmpty(senders.toString())) {
						String addStr = ((InternetAddress) address)
								.getAddress();
						senders.append(StringUtils.trimToNull(addStr));
					} else {
						String addStr = ((InternetAddress) address)
								.getAddress();
						senders.append(", ");
						senders.append(StringUtils.trimToNull(addStr));
					}
				} else {
					if (StringUtils.isEmpty(senders.toString())) {
						String addStr = address.toString();
						senders.append(StringUtils.trimToNull(addStr));
					} else {
						String addStr = address.toString();
						senders.append(", ");
						senders.append(StringUtils.trimToNull(addStr));
					}
				}
			}
		}
		return senders.toString();
	}

	/**
	 * Return the body of the message.
	 * 
	 * 
	 * @param part
	 * @return String
	 * @throws IOException
	 * @throws MessagingException
	 */
	private String getText(Part part) throws IOException, MessagingException {
		Object content = part.getContent();
		if (content instanceof Multipart) {
			for (int i = 0; i < ((Multipart) content).getCount(); i++) {
				part = ((Multipart) content).getBodyPart(i);

				if (part.isMimeType("text/plain")) {
					return StringUtils.trimToNull((String) part.getContent());
				}
			}
		} else {
			if (part.isMimeType("text/plain")) {
				return StringUtils.trimToNull((String) part.getContent());
			}
		}
		return null;
	}

	/**
	 * @return the sslEnabled
	 */
	public boolean isSSLEnabled() {
		return sslEnabled;
	}

	public void openFolder(String folderName) throws MessagingException {
		if (logger.isDebugEnabled()) {
			logger.debug("Opening " + folderName);
		}
		folder = store.getDefaultFolder();
		folder = folder.getFolder(folderName);

		if (folder == null) {
			if (logger.isInfoEnabled()) {
				logger.info("Folder " + folderName + "cannot be opened.");
			}
			throw new MessagingException("Invalid folder");
		}

		try {
			folder.open(Folder.READ_WRITE);
		} catch (MessagingException ex) {
			folder.open(Folder.READ_ONLY);
		}

	}

	/**
	 * @param host
	 *            the host to set
	 */
	public void setHost(String host) {
		this.host = host;
	}

	/**
	 * @param password
	 *            the password to set
	 */
	public void setPassword(String password) {
		this.password = password;
	}

	/**
	 * @param port
	 *            the port to set
	 */
	public void setPort(int port) {
		this.port = port;
	}

	/**
	 * @param protocol
	 *            the protocol to set
	 */
	public void setProtocol(String protocol) {
		this.protocol = protocol;
	}

	/**
	 * @param session
	 *            the session to set
	 */
	public void setSession(Session session) {
		this.session = session;
	}

	/**
	 * @param sslEnabled
	 *            the sslEnabled to set
	 */
	public void setSSLEnabled(boolean sslEnabled) {
		this.sslEnabled = sslEnabled;
	}

	/**
	 * @param username
	 *            the username to set
	 */
	public void setUsername(String username) {
		this.username = username;
	}
}